#!/bin/bash

#
# 初始化：加载环境变量，初始化部分变量
#

init ()
{
    if [ -f /etc/bashrc ];then
        source /etc/bashrc &> /dev/null
    elif [ -f /etc/profile ];then
        source /etc/profile &> /dev/null
    fi
    export LANG=en_US.UTF-8
    [ -o braceexpand ] || set -B

    #
    # 日志相关：日志文件创建，日志级别定义
    #
    thisFilePath=$(readlink -e "$0")
    currentDate=$(date +'%Y%m%d')
    SCRIPT_NAME=${thisFilePath##*/}
    pidfile="${thisFilePath%/*}/.${SCRIPT_NAME%.*}.pid"
    LOG_FILE="${thisFilePath%/*}/${SCRIPT_NAME%.*}_${currentDate}.log"
    [ -f "${LOG_FILE}" ] || touch "${LOG_FILE}"
    if [ ! -w "${LOG_FILE}" ]; then
        LOG_FILE="/tmp/${SCRIPT_NAME%.*}_${currentDate}.log"
        touch "${LOG_FILE}" || { echo "Failed to create log file in /tmp"; exit 71; }
    fi

    # salt -l LOG_LEVEL, --log-level=LOG_LEVEL
    #+----------+---------+--------------------------------------------------------------------------+
    #|Level     |Numeric  |value Description                                                         |
    #+----------+---------+--------------------------------------------------------------------------+
    #|quiet     |1000     |Nothing should be logged at this level                                    |
    #|critical  |50       |Critical errors                                                           |
    #|error     |40       |Errors                                                                    |
    #|warning   |30       |Warnings                                                                  |
    #|info      |20       |Normal log information                                                    |
    #|profile   |15       |Profiling information on salt performance                                 |
    #|debug     |10       |Information useful for debugging both salt implementations and salt code  |
    #|trace     |5        |More detailed code debugging information                                  |
    #|garbage   |1        |Even more debugging information                                           |
    #|all       |0        |Everything                                                                |
    #+----------+---------+--------------------------------------------------------------------------+

    #declare -A LOG_LEVEL
    #LOG_LEVEL['all']=0
    #LOG_LEVEL['debug']=10
    declare -gA LOG_LEVEL='([error]="40" [warning]="30" [quiet]="1000" [info]="20" )'

    declare -g NO_COLOR="false"
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[0;33m'
    NC='\033[0m' # No Color
    info="${GREEN}INFO   ${NC}"
    warning="${YELLOW}WARNING${NC}"
    error="${RED}ERROR  ${NC}"

    #
    # 设置脚本选项：-l, --log-level=LOG_LEVEL / --no-color
    #

    duplicate_option ()
    {
        echo "Duplicate option '$1' detected." >&2
        exit 1
    }

    set_option ()
    {
        # 初始化变量
        #OPTION_LOG_LEVEL=""

        # getopt 有版本区别 最好用增强版（getopt -T;echo $? 输出为4） getopt 命令一般在 util-linux 包中  rpm -ql util-linux | grep getopt
        # getopt 会自己判断无效选项，所以无需自己判断了
        # 使用 getopt 生成一个重新排列的参数列表
        TEMP=`getopt -o hl: --long log-level:,no-color,help -n "${SCRIPT_NAME}:L${BASH_LINENO[0]}:getopt" -- "$@"`
        if [ $? != 0 ]; then
            echo "Terminating..." >&2
            exit 1
        fi

        # 重新排列参数
        eval set -- "$TEMP"

        # 处理选项
        while true ; do
            case "$1" in
                -l|--log-level)
                    [ -n "$OPTION_LOG_LEVEL" ] && duplicate_option
                    # 当为::时，才表示可选的，且argument必须紧贴选项，例如-carg而不能是-c arg，--clong=arg而不能是--clong arg，所以逻辑可优化
                    case "$2" in
                        "") shift 2 ;;
                        *) OPTION_LOG_LEVEL=$2 ; shift 2 ;;
                    esac ;;
                --no-color) NO_COLOR="true" ; shift ;;
                -h|--help)
                    echo "Usage: ${0##*/} [-l, --log-level=LOG_LEVEL|--no-color|-h, --help]"
                    echo "       LOG_LEVEL: ${!LOG_LEVEL[@]}"
                    exit 0
                    ;;
                --) shift ; break ;;
            esac
        done

        # 处理剩余的参数
        #echo "--${@}--"
        #shift $[$OPTIND -1 ]
        #echo "--${@}--"
        FINAL_LOG_LEVEL="${LOG_LEVEL[${OPTION_LOG_LEVEL:-info}]}"

        # 返回处理后的参数，不包括选项
        declare -g ARGS_LEFT=("${@}")
        return 0
    }

    flog ()
    {
        #echo ${1,,} ${!LOG_LEVEL[@]} ${LOG_LEVEL[@]}
        if [ "${LOG_LEVEL[${1,,}]}" -ge "${FINAL_LOG_LEVEL:-20}" ];then
            local level="$1"
            shift
            printf "%s [L%3s] [%-7s]: %s\n" "$(date +'%b %e %T')" "${BASH_LINENO[0]}" "${level^^}" "$*" &>> ${LOG_FILE}
            if "${NO_COLOR}";then
                printf "%s [L%3s] [%-7s]: %s\n" "$(date +'%b %e %T')" "${BASH_LINENO[0]}" "${level^^}" "$*"
            else
               printf "%s [L%3s] [${!level}]: %s\n" "$(date +'%b %e %T')" "${BASH_LINENO[0]}" "$*"
            fi
        fi
        # echo -e "$(date +'%b %e %T') [L${BASH_LINENO[0]}] [${!level}]: $*"'\r'
    }

    #
    # 单进程相关
    #

    running_process_check ()
    {
        ## 兼容 `nuhup $0 &`  `bash $0`  `sh $0`  `$0` `vim $0`等执行情况
        if [[ -f "${pidfile}" ]]; then
            if kill -0 "$(cat "${pidfile}")" >/dev/null 2>&1; then
                flog warning "other processes are running"
                pgrep -f "${SCRIPT_NAME}" | xargs -r ps -opid,etimes,cmd | tee -a "${LOG_FILE}"
                exit 1
            fi
        fi
        echo $$ > "${pidfile}" || exit 1
    }

}
------------------------------------------------------------------------------------------
#!/bin/bash

source ./share_init.sh
#
# 测试代码
#
run_test ()
{
    flog info "this script: ${BASH_SOURCE}"
    flog info '123' 'adc' 'iiiii'
    flog warning 'test warn stand output'
    flog error 'test error stand output' >&2
    flog info "done in ${SECONDS}s"
}

init
set_option "$@"
#ARGS_LEFT=$(set_option "$@")
running_process_check
run_test

echo "--${ARGS_LEFT[@]}--"
#echo "--${@}--"
#------------------------------------------------------------------------------------------
#echo "Upload files via FTP or SFTP based on the lftp command
#
#Usage:
#  $(basename $0) [OPTION]... [FILES]...
#
#Mandatory arguments to long options are mandatory for short options too.
#  -h, --host=HOSTNAME        specifies the remote host to connect to
#  -P, --port=PORT            specifies the port to connect to
#  -u, --username=USERNAME    specifies the user to log in as
#  -p, --password=PASSWORD    identify yourself to the remote host
#  -s, --sftp                 transfer files using sftp, default ftp
#  -m, --mkdir                create a directory on the remote host
#  -a, --ascii                use ascii mode (binary is the default)
#  -d, --upload-dir=DIR       upload files to the specified directory
#                             with -m, create the directory first
#      --help                 display this help and exit
#
#Exit status:
# 0   if OK,
# 2   if error running (e.g., duplicate options)."




trap 'flog info "Script done in ${SECONDS}s"' EXIT

ps_expanded_SQL ()
{
    # format `ps -o lstart,pid,ucmd,user,args` like postgres `\x toggle expanded output`  doris `\G`
    awk -vOFS=$'\x1f' '
    function _trim(start,end) {
        if (end == "") {str = substr($0, start+1)}
        else {str = substr($0, start+1, end-start-1)};
        gsub(/^ +| +$/,"",str);
        return str;
    }
    NR==1 {
        a=index($0,"STARTED")+7;
        b=index($0,"PID")+3;
        c=index($0,"USER");
        d=index($0,"COMMAND")
    } { print _trim(0,a),_trim(a,b),_trim(b,c-1),_trim(c-1,d-1),_trim(d-1)
    }' | awk -F$'\x1f' '{
    LEN=LEN<NF?NF:LEN;
    for(i=1;i<=NF;i++){
        a[NR,i]=$i;CL=(NR==1 && CL<length($i))?length($i):CL}
    }
    END {
        for(k=2;k<=NR;k++)
            for(j=1;j<=LEN;j++)
            {printf j==1?">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"RS:"";
            printf "%"CL"s: %s",a[1,j],a[k,j]RS }
        }'
}

fmt_sql ()
{
    # separator 分隔符
    separator="${1}";
    shift;
    if [ "$#" -eq 0 ]; then
        local lines="";
        while IFS= read -r line; do
            lines+="$line"$'\n';
        done;
        local tabstr="${lines%$'\n'*}";
    else
        tabstr="$*";
    fi;
    # tabstr 格式化的文本
    unset idxs;
    # idxs 起始行号    nfc 字段数与上一行不相同 拆分 进行分段（每段文本 _txt  每段字段数 field_cnt）格式化
    for _el in `echo "$tabstr" | awk -F"${separator}" '{nfc=nfc==""?NF:nfc;printf NF==nfc?"":NR-1" ";nfc=NF} END {print NR}'`;
    do
        idxs=${idxs:-1};
        _txt=`echo "$tabstr" | sed -n "$idxs,$_el p"`;
        idxs=$((_el+1));
        field_cnt=`echo "${_txt}" | head -1 | awk -F"${separator}" '{print NF}'`;
        _segment=$(printf "+\x1f%.0s" `seq "$field_cnt"`;echo -n '+');
        echo "${_txt}" | sed -E -e "s#^#|#g" -e "s%${separator}|$%\x1f|%g" -e "1,+1 i ${_segment}" -e "$ a ${_segment}" | column -s$'\x1f' -t -o ' ' | awk '{if($0 ~ /^+/){gsub(" ","-",$0)} print $0}';
    done
}

ps -o lstart,pid,comm,cmd `pgrep -f 'doris_be|DorisFE'` | awk -vOFS=$'\x1f' 'function _trim(start,end){ if (end == "") {str = substr($0, start+1)} else {str = substr($0, start+1, end-start-1)};gsub(/^ +| +$/,"",str);return str; } NR==1 {a=index($0,"STARTED")+7;b=index($0,"PID")+3;c=index($0,"CMD")} {print _trim(0,a),_trim(a,b),_trim(b,c),_trim(c-1,c+141)}' | fmt_sql $'\x1f'

ps -o lstart,pid,comm,cmd `pgrep -f 'doris_be|DorisFE'` | ps_expanded_SQL

ps -C node -o lstart,pid,ucmd,user:13,args | awk 'NR==1 || /kibana-6.6.1-linux-x86_64/' | ps_expanded_SQL &>> "${LOG_FILE}"
/home/mr/mysql-5.7.22-linux-glibc2.12-x86_64/bin/mysql -uadmin -P9030 -h127.0.0.1 -e 'show frontends;show backends;' | awk -v OFS=',' -F'\t' '$0~/^Name|^BackendId/ {delete a;for (i=1;i<=NF;i++) if ($i ~ /Host|IsMaster|Alive|Version/) a[$i]=i; n=asort(a, idx)} {for (i=1; i<=n; i++) printf i==1?"%s":",%s",$(idx[i]);print ""}' | fmt_sql ,

awk 处理文本：行转列       ? : 三元表达式   awk: cmd. line:1: (FILENAME=- FNR=3) fatal: not enough arguments to satisfy format string
awk '{for(i=1;i<=NF;i++)a[NR,i]=$i}END{for(j=1;j<=NF;j++)for(k=1;k<=NR;k++)printf k==NR?a[k,j] RS:a[k,j] FS}'   ## j<NF 这个NF是最后一行的NF个数，会打印不全
awk '{LEN=LEN<NF?NF:LEN;for(i=1;i<=NF;i++){a[NR,i]=$i}}END{for(j=1;j<=LEN;j++)for(k=1;k<=NR;k++)printf k==NR?a[k,j] RS:a[k,j] FS}'
awk '{LEN=LEN<NF?NF:LEN;for(i=1;i<=NF;i++){a[NR,i]=$i}}END{for(j=1;j<=LEN;j++)for(k=1;k<=NR;k++)printf "%s",k==NR?a[k,j] RS:a[k,j] FS}'   ## 字段值中包含 %s 会提示：参数数量少于格式数量
awk -F, '{LEN=LEN<NF?NF:LEN;for(i=1;i<=NF;i++){a[NR,i]=$i;CL=(NR==1 && CL<length($i))?length($i):CL}}END{for(k=2;k<=NR;k++)for(j=1;j<=LEN;j++){printf j==1?"*************************** "k-1". row ***************************"RS:"";printf "%"CL"s: %s",a[1,j],a[k,j]RS }}'   ## 行转列 像数据库一样 \x 一样
                    列转行       ? : 三元表达式
awk '{for(i=0;++i<=NF;)a[i]=a[i]?a[i] FS $i:$i}END{for(i=0;i++<NF;)print a[i]}'
awk '{LEN=LEN<NF?NF:LEN;for(i=0;++i<=NF;){a[i]=a[i]?a[i] FS $i:$i}}END{for(i=0;i++<LEN;)print a[i]}'

sed '1i * * * * * root echo 1' /etc/crontab| grep -vE 'MAILTO|PATH|SHELL|^\s*(#|$)' | sed "$(printf "s/\s\+/#/%d;" {5..1..-1})" |  awk -F'#' '{OFS="#";usr=index($6," ")>0?substr($6,1,index($6," ")-1):$6;cmd="id -un -- "usr" &>/dev/null"; if (system(cmd)!=0){$5=$5"#-"}else{sub(" ","#",$6)} ;print $0}' | sed '1i mm#hh24#day#month#week#user#cmd'| output_sql_format '#'

crontab -l -u root | grep -vE 'MAILTO|PATH|SHELL|^\s*(#|$)' | sed "$(printf "s/\s\+/#/%d;" {5..1..-1})" | grep -v MAILTO | sed '1i min#h#d#m#w#command'| output_sql_format '#' | sed '1i\
# .---------------- minute (0 - 59)\
# |  .------------- hour (0 - 23)\
# |  |  .---------- day of month (1 - 31)\
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...\
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat\
# |  |  |  |  |\
# *  *  *  *  * [user-name]  command to be executed'
# curl wget tcpdump iptables ethtool lftp tcl语言
        man 8 iptables-extensions | grep -E '^ {3}[[:alnum:]]|^ {7}[-\[]'

# es
# logstash
# kibana
# grafana
# salt
# kafka
# doris
# Prometheus
# postgres
# hadoop
# spark

# docker
# k8s
# golang

linux 源码
/usr/src/kernels/3.10.0-693.21.1.el7.x86_64/include/uapi/asm-generic/errno{-base,}.h  ## errno 或者  error

man proc  关于/proc的信息

显示什么类型的手册，由 man 和命令中间的数字决定，目前共有 9 个 man 支持的数字。
数字	说明
1	可执行程序或 Shell 命令
2	系统调用（内核提供的函数）
3	库调用
4	特殊文件（通常位于 /dev 目录）
5	文件格式和约定（比如 /etc/passwd）
6	游戏
7	杂项（包和一些约定）Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)
8	系统管理命令（通常是 root 用户执行的命令）
9	内核相关的文件 Kernel routines [Non standard]
系统调用
https://zhuanlan.zhihu.com/p/344311940

内存泄漏
valgrind -s  --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes --log-file=./xielou_check.log  ./mem_xielou.sh

ELF LSB 逆向 反编译 https://52sbl.cn/discussion/25926.html    /bin/ls
https://zhuanlan.zhihu.com/p/347262004
命令	说明
readelf -h	读取ELF文件头
readelf -S	查看段的属性
readelf -s	查看符号表
objdump -h	查看段的属性
objdump -s	将所有段的内容以十六进制打印出来
objdump -d	将所有包含的指令反汇编
objdump -r	查看重定位段

段名	说明
.text	代码段。存放可执行文件的指令，这部分区域在程序运行前就已经确定。通过 objdump -s -d 查看
.data	数据段。保存已经初始化（非零初始化）的全局变量和静态局部变量
.bss	bss段。未初始化（零初始化）的全局变量和静态局部变量保存在bss段，准确来说.bss段为他们预留了位置，等到最终链接时在分配到.bss段（具体和编译器有关）
.rodata	只读数据段。存放的是只读数据，一般是程序里面的只读变量(const修饰的变量)和字符串变量（printf 的格式化字符也算）
.comment	存放的是编译器的版本信息
.symtab	符号表段。用来定位、重定位程序中符号定义和引用的信息，简单的理解就是符号表记录了该文件中的所有符号，所谓的符号就是经过修饰了的函数名或者变量名，不同的编译器有不同的修饰规则。
.shstrtab	字符串表段。存放着所有符号的名称字符串
.dynamic	动态链接信息。




